package com.jsync.system;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import net.contentobjects.jnotify.JNotify;
import net.contentobjects.jnotify.JNotifyListener;

import com.jsync.model.Resource;
import com.jsync.model.ResourceProfile;
import com.jsync.model.ResourceQueue;
import com.jsync.service.ResourceQueueManager;
import com.jsync.util.SingletonFactory;

/**
 * @author prabz
 * 
 */
public class WindowsFileListener {

	private List<ResourceProfile> profiles;
	private Map<Integer, ResourceProfile> watchIds;
	private ResourceQueueManager queueManager;

	/**
	 * @param rootPath
	 * @return
	 */
	private List<ResourceProfile> getProfiles(String rootPath) {
		List<ResourceProfile> rps = new ArrayList<ResourceProfile>();
		for (ResourceProfile rp : profiles) {
			if (rp.getSource().equals(rootPath))
				rps.add(rp);
		}
		return rps;
	}

	/**
	 * @param msg
	 */
	private void print(String msg) {
		System.err.println(msg);
	}

	/**
	 * @param resource
	 * @return
	 */
	private boolean isFiltered(Resource resource) {
		boolean flag = false;
		String filters = resource.getProfile().getFilters();
		if (filters != null && filters.trim().length() > 0) {
			String[] types = filters.split(";");
			for (int i = 0; i < types.length; i++) {
				flag = resource.getName().matches(types[i].replace(".", "\\.").replace("*", ".*"));
				if(flag)break;
			}
		}
		return flag;
	}

	/**
	 * 
	 */
	public WindowsFileListener() {
		profiles = new ArrayList<ResourceProfile>();
		watchIds = new HashMap<Integer, ResourceProfile>();
		queueManager = (ResourceQueueManager) SingletonFactory.getInstance()
				.get(ResourceQueueManager.class);
	}

	/**
	 * @param profile
	 * @throws Exception
	 */
	public boolean watch(ResourceProfile profile) {
		boolean flag = false;
		try {
			// path to watch
			String path = profile.getSource();

			// watch mask, specify events you care about,
			// or JNotify.FILE_ANY for all events.
			int mask = JNotify.FILE_CREATED | JNotify.FILE_DELETED
					| JNotify.FILE_MODIFIED | JNotify.FILE_RENAMED;

			// watch subtree?
			boolean watchSubtree = true;

			// add actual watch
			int watchID = JNotify.addWatch(path, mask, watchSubtree,
					new JFileListener());

			// save the profile and watch id
			profiles.add(profile);
			watchIds.put(watchID, profile);

			System.out.println("Adding profile: " + profile
					+ " success -- WatchID: " + watchID);

			// sleep a little, the application will exit if you
			// don't (watching is asynchronous), depending on your
			// application, this may not be required
			// Thread.sleep(1000000);

			// to remove watch the watch
			// boolean res = JNotify.removeWatch(watchID);
			// if (!res) {
			// invalid watch ID specified.
			// }

			flag = true;
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return flag;
	}

	/**
	 * @param profile
	 * @return
	 * @throws Exception
	 */
	public boolean removeWatch(ResourceProfile profile) throws Exception {
		boolean res = false;
		synchronized (watchIds) {
			for (Iterator<Integer> it = watchIds.keySet().iterator(); it
					.hasNext();) {
				Integer watchId = it.next();
				if (profile.equals(watchIds.get(watchId))) {
					// to remove watch the watch
					res = JNotify.removeWatch(watchId);
					if (!res) {
						// invalid watch ID specified.
					} else {
						it.remove();
					}
				}
			}
		}
		return res;
	}

	/**
	 * @throws Exception
	 */
	public void stop() throws Exception {
		synchronized (profiles) {
			for (Iterator<ResourceProfile> it = profiles.iterator(); it
					.hasNext();) {
				ResourceProfile rp = it.next();
				if (removeWatch(rp)) {
					it.remove();
					System.out.println("Removing profile: " + rp + " success");
				} else
					System.out.println("Removing profile: " + rp + " failed");
			}
		}
	}

	private class JFileListener implements JNotifyListener {
		/*
		 * (non-Javadoc)
		 * 
		 * @see net.contentobjects.jnotify.JNotifyListener#fileRenamed(int,
		 * java.lang.String, java.lang.String, java.lang.String)
		 */
		public void fileRenamed(int wd, String rootPath, String oldName,
				String newName) {
			for (ResourceProfile rp : getProfiles(rootPath)) {
				ResourceQueue queue = queueManager.getResourceQueue(rp);
				Resource resource = new Resource();
				resource.setProfile(rp);
				resource.setName(oldName);
				resource.setNewName(newName);
				resource.setChange(Resource.Operation.RENAME);
				if (!isFiltered(resource)){
					print("renamed " + rootPath + " : " + oldName + " -> " + newName
							+ " ===>wd: " + wd);					
					queue.add(resource);
				}
			}
			((ResourceMonitor) SingletonFactory.getInstance().get(
					ResourceMonitor.class)).notify();
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see net.contentobjects.jnotify.JNotifyListener#fileModified(int,
		 * java.lang.String, java.lang.String)
		 */
		public void fileModified(int wd, String rootPath, String name) {			
			for (ResourceProfile rp : getProfiles(rootPath)) {
				ResourceQueue queue = queueManager.getResourceQueue(rp);
				Resource resource = new Resource();
				resource.setProfile(rp);
				resource.setName(name);
				resource.setChange(Resource.Operation.MODIFY);
				if (!isFiltered(resource)){
					print("modified " + rootPath + " : " + name + " ===>wd: " + wd);
					queue.add(resource);
				}
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see net.contentobjects.jnotify.JNotifyListener#fileDeleted(int,
		 * java.lang.String, java.lang.String)
		 */
		public void fileDeleted(int wd, String rootPath, String name) {			
			for (ResourceProfile rp : getProfiles(rootPath)) {
				ResourceQueue queue = queueManager.getResourceQueue(rp);
				Resource resource = new Resource();
				resource.setProfile(rp);
				resource.setName(name);
				resource.setChange(Resource.Operation.DELETE);
				if (!isFiltered(resource)){
					print("deleted " + rootPath + " : " + name + " ===>wd: " + wd);
					queue.add(resource);
				}
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see net.contentobjects.jnotify.JNotifyListener#fileCreated(int,
		 * java.lang.String, java.lang.String)
		 */
		public void fileCreated(int wd, String rootPath, String name) {			
			for (ResourceProfile rp : getProfiles(rootPath)) {
				ResourceQueue queue = queueManager.getResourceQueue(rp);
				Resource resource = new Resource();
				resource.setProfile(rp);
				resource.setName(name);
				resource.setChange(Resource.Operation.ADD);
				if (!isFiltered(resource)){
					print("created " + rootPath + " : " + name + " ===>wd: " + wd);
					queue.add(resource);
				}
			}
		}
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		WindowsFileListener wfl = new WindowsFileListener();
		try {
			ResourceProfile rp = new ResourceProfile();
			rp.setSource("C://test");
			wfl.watch(rp);
			rp = new ResourceProfile();
			rp.setSource("C://test1");
			wfl.watch(rp);
			Thread.sleep(100000);
			wfl.stop();
		} catch (Exception ex) {
			ex.printStackTrace();
		}

	}
}